package com.powernode.ssm.serviceImpl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.powernode.ssm.dao.UserLoginAttemptMapper;
import com.powernode.ssm.pojo.po.UserLoginAttempt;
import com.powernode.ssm.service.UserLoginAttemptService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;

/**
 * Service 实现类：基于 user_login_attempt 表，实现 IP 登录频控逻辑（Redis 降级方案）
 * <p>
 * 包含以下核心功能：
 * - 查询 IP 记录
 * - 判断是否允许登录（是否超频/被封禁）
 * - 记录登录尝试（更新计数/触发封禁）
 * - 封禁/解封 IP
 * - 重置短期/长期尝试窗口
 * </p>
 */
@Slf4j
@Service
public class UserLoginAttemptServiceImpl implements UserLoginAttemptService {

    @Autowired
    private UserLoginAttemptMapper userLoginAttemptMapper;

    // ======== 配置参数（可抽取到 application.yml 中，使用 @Value 注入）========
    private static final int SHORT_MAX_ATTEMPTS = 5;     // 短期最大尝试次数，如 60 秒内
    private static final int LONG_MAX_ATTEMPTS = 20;     // 长期最大尝试次数，如 10 分钟内
    private static final int BLOCK_DURATION_MINUTES = 60; // 封禁时长（分钟）

    private static final int SHORT_WINDOW_SECONDS = 60;   // 短期窗口：60 秒
    private static final int LONG_WINDOW_MINUTES = 10;    // 长期窗口：10 分钟

    /**
     * 根据 IP 查询登录尝试记录
     */
    @Override
    public UserLoginAttempt findByIp(String ip) {
        return userLoginAttemptMapper.findByIp(ip);
    }

    /**
     * 判断该 IP 是否允许登录
     */
    @Override
    public boolean isLoginAllowed(String ip) {
        UserLoginAttempt attempt = userLoginAttemptMapper.findByIp(ip);
        if (attempt == null) {
            return true; // 无记录，允许登录
        }

        LocalDateTime now = LocalDateTime.now();

        // 1. 是否已被封禁且未过期
        if (attempt.getIsBlocked() != null && attempt.getIsBlocked() == 1) {
            if (attempt.getBlockExpireTime() != null && attempt.getBlockExpireTime().isAfter(now)) {
                log.warn("[降级频控] IP = {} 当前被封禁，直到 {}", ip, attempt.getBlockExpireTime());
                return false;
            } else {
                // 封禁已过期，自动解封
                resetBlockStatus(ip, now);
            }
        }

        // 2. 检查短期尝试次数是否超限
        if (attempt.getShortAttempts() != null && attempt.getShortAttempts() >= SHORT_MAX_ATTEMPTS) {
            if (attempt.getShortExpireTime() != null && attempt.getShortExpireTime().isAfter(now)) {
                log.warn("[降级频控] IP = {} 短期尝试超限（{} >= {}），触发封禁", ip, attempt.getShortAttempts(), SHORT_MAX_ATTEMPTS);
                blockIp(attempt, ip, now);
                return false;
            } else {
                resetShortWindow(ip, now);
            }
        }

        // 3. 检查长期尝试次数是否超限
        if (attempt.getLongAttempts() != null && attempt.getLongAttempts() >= LONG_MAX_ATTEMPTS) {
            if (attempt.getLongExpireTime() != null && attempt.getLongExpireTime().isAfter(now)) {
                log.warn("[降级频控] IP = {} 长期尝试超限（{} >= {}），触发封禁", ip, attempt.getLongAttempts(), LONG_MAX_ATTEMPTS);
                blockIp(attempt, ip, now);
                return false;
            } else {
                resetLongWindow(ip, now);
            }
        }

        // 未超限，允许登录
        return true;
    }

    /**
     * 记录一次登录尝试，更新计数，可能触发封禁
     */
    @Override
    public void recordLoginAttempt(String ip) {
        UserLoginAttempt attempt = userLoginAttemptMapper.findByIp(ip);
        LocalDateTime now = LocalDateTime.now();

        if (attempt == null) {
            attempt = new UserLoginAttempt();
            attempt.setIp(ip);
            attempt.setShortAttempts(1);
            attempt.setLongAttempts(1);
            attempt.setIsBlocked(0);
            attempt.setShortExpireTime(now.plusSeconds(SHORT_WINDOW_SECONDS));
            attempt.setLongExpireTime(now.plusMinutes(LONG_WINDOW_MINUTES));
        } else {
            // 更新短期
            if (attempt.getShortExpireTime() != null && attempt.getShortExpireTime().isAfter(now)) {
                attempt.setShortAttempts(attempt.getShortAttempts() + 1);
            } else {
                attempt.setShortAttempts(1);
                attempt.setShortExpireTime(now.plusSeconds(SHORT_WINDOW_SECONDS));
            }

            // 更新长期
            if (attempt.getLongExpireTime() != null && attempt.getLongExpireTime().isAfter(now)) {
                attempt.setLongAttempts(attempt.getLongAttempts() + 1);
            } else {
                attempt.setLongAttempts(1);
                attempt.setLongExpireTime(now.plusMinutes(LONG_WINDOW_MINUTES));
            }
        }

        userLoginAttemptMapper.upsertAttempt(
                attempt.getIp(),
                attempt.getShortAttempts(),
                attempt.getLongAttempts(),
                attempt.getIsBlocked(),
                attempt.getShortExpireTime(),
                attempt.getLongExpireTime(),
                attempt.getBlockExpireTime()
        );
    }

    /**
     * 封禁 IP（设置 is_blocked = 1，记录封禁时间）
     */
    @Override
    public void blockIp(UserLoginAttempt attempt, String ip, LocalDateTime now) {
        attempt.setIsBlocked(1);
        attempt.setBlockExpireTime(now.plusMinutes(BLOCK_DURATION_MINUTES));
        userLoginAttemptMapper.upsertAttempt(
                ip, attempt.getShortAttempts(), attempt.getLongAttempts(),
                1, attempt.getShortExpireTime(), attempt.getLongExpireTime(), attempt.getBlockExpireTime()
        );
        log.warn("[降级频控] IP = {} 已被封禁，直到 {}", ip, attempt.getBlockExpireTime());
    }

    /**
     * 解封 IP（设置 is_blocked = 0，清除封禁时间）
     */
    @Override
    public void resetBlockStatus(String ip, LocalDateTime now) {
        userLoginAttemptMapper.upsertAttempt(
                ip, 0, 0, 0, null, null, null
        );
        log.info("[降级频控] IP = {} 封禁已过期，自动解封", ip);
    }

    /**
     * 重置短期尝试窗口（如 60 秒已过，重置短期计数）
     */
    @Override
    public void resetShortWindow(String ip, LocalDateTime now) {
        userLoginAttemptMapper.upsertAttempt(
                ip, 0, 0, 0, null, null, null
        );
        log.info("[降级频控] IP = {} 短期窗口已过，重置计数", ip);
    }

    /**
     * 重置长期尝试窗口（如 10 分钟已过，重置长期计数）
     */
    @Override
    public void resetLongWindow(String ip, LocalDateTime now) {
        userLoginAttemptMapper.upsertAttempt(
                ip, 0, 0, 0, null, null, null
        );
        log.info("[降级频控] IP = {} 长期窗口已过，重置计数", ip);
    }
}